﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;

[assembly: WebResource("PTA.Res.Web.JQueryForm.js", "text/javascript")]
namespace PTA.Web
{
    #region Ajax方法特性, Ajax请求类型(GET,POST), 返回值序列化方式(XML,JSON,NONE)

    public abstract class BaseAjaxMethodAttribute : Attribute
    {
        private string clientFunction = string.Empty;
        /// <summary>
        /// 客户端方法,用来解决方法同名的问题,客户端脚本中使用这个方法来调用.
        /// </summary>
        public string ClientFunction
        {
            get { return this.clientFunction; }
            set { this.clientFunction = value; }
        }
    }

    /// <summary>
    /// 标识一个方法是一个可以使用客户端AJAX请求调用的方法
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class AjaxMethodAttribute : BaseAjaxMethodAttribute
    {
        #region 私有变量
        private string contentType;
        private AjaxRequestType method = AjaxRequestType.POST;
        private SerializeType stype = SerializeType.JSON;
        private bool checkUrl = true;
        private string authUrl = string.Empty;
        private int authPrivilege = 0;
        #endregion
        public AjaxMethodAttribute()
        {
        }

        public AjaxMethodAttribute(SerializeType serializeType)
        {
            this.SerializeType = serializeType;
        }

        public AjaxMethodAttribute(SerializeType serializeType, AjaxRequestType requestType)
        {
            this.SerializeType = serializeType;
            this.RequestType = requestType;
        }

        /// <summary>
        /// 输出内容的类型
        /// </summary>
        public string ContentType
        {
            get
            {
                return this.contentType;
            }
            set
            {
                this.contentType = value;
            }
        }

        /// <summary>
        /// Ajax请求的方式
        /// </summary>
        public AjaxRequestType RequestType
        {
            get
            {
                return this.method;
            }
            set
            {
                this.method = value;
            }
        }

        /// <summary>
        /// 返回结果的序列化方式,默认使用TEXT
        /// </summary>
        public SerializeType SerializeType
        {
            get
            {
                return this.stype;
            }
            set
            {
                this.stype = value;
            }
        }

        /// <summary>
        /// 是否验证方法定义页面的URL
        /// </summary>
        public bool CheckAuth
        {
            get
            {
                return this.checkUrl;
            }
            set
            {
                this.checkUrl = value;
            }
        }

        public string AuthUrl
        {
            get
            {
                return this.authUrl;
            }
            set
            {
                this.authUrl = value;
            }
        }

        public int AuthPrvg
        {
            get
            {
                return this.authPrivilege;
            }
            set
            {
                this.authPrivilege = value;
            }
        }

    }

    /// <summary>
    /// 标识一个方法是一个可以使用客户端From提交AJAX请求调用的方法
    /// </summary>
    public class AjaxFormMethodAttribute : AjaxMethodAttribute
    {
        public AjaxFormMethodAttribute()
        {
        }
        public AjaxFormMethodAttribute(SerializeType serializeType)
            : base(serializeType)
        {
        }
        public AjaxFormMethodAttribute(SerializeType serializeType, AjaxRequestType requestType)
            : base(serializeType, requestType)
        {
        }
    }

    [AttributeUsage(AttributeTargets.Method)]
    public class JsonpMethodAttribute : BaseAjaxMethodAttribute
    {
        public JsonpMethodAttribute(string handler)
            : this(handler, null)
        {

        }
        public JsonpMethodAttribute(string handler, string callback)
        {
            this._AjaxHandlerUrl = handler;
            this._callback = callback;
        }

        private string _AjaxHandlerUrl = string.Empty;
        public string AjaxHandlerUrl
        {
            get
            {
                return this._AjaxHandlerUrl;
            }
            set
            {
                this._AjaxHandlerUrl = value;
            }
        }

        private string _callback;
        public string Callback
        {
            get
            {
                return this._callback;
            }
            set
            {
                this._callback = value;
            }
        }

        internal AjaxRequestType RequestType
        {
            get
            {
                return AjaxRequestType.GET;
            }
        }

        /// <summary>
        /// 返回结果的序列化方式,默认使用TEXT
        /// </summary>
        internal string SerializeType
        {
            get
            {
                return "jsonp";
            }
        }
    }

    [AttributeUsage(AttributeTargets.Parameter)]
    public class ParameterParseAttribute : Attribute
    {
        public ParameterParseAttribute(string errorText)
        {
            this._errorText = errorText;
        }

        private string _errorText = string.Empty;
        public string ErrorText
        {
            get { return this._errorText; }
            set { this._errorText = value; }
        }
    }

    /// <summary>
    /// 返回结果的序列化方法
    /// </summary>
    public enum SerializeType
    {
        /// <summary>
        /// 不序列化内容，如果方法返回的结果为String类型，则直接输出到客户端，否则调整对象的ToStirng()方法，将结果返回到客户端
        /// </summary>
        NONE,

        /// <summary>
        /// 将方法的结果以XML的方式序列化后，输出到客户端
        /// </summary>
        XML,

        /// <summary>
        /// 将方法的结果以JSON的方式序列化后，输出到客户端
        /// </summary>
        JSON,
    }

    /// <summary>
    /// Ajax请求的方式
    /// </summary>
    public enum AjaxRequestType
    {
        GET,
        POST
    }
    #endregion

    public class AjaxHandler : IHttpHandler, IRequiresSessionState
    {

        public bool IsReusable
        {
            get { return false; }
        }

        public void ProcessRequest(HttpContext context)
        {
            AjaxUtility.ProcessRequest(context);
        }
    }

    /// <summary>
    /// Ajax处理类
    /// <para>
    /// 要使用这个类,需要在web.config中来配置拦截HTTP请求的httpHandler,方法是在httpHandlers节点中添加如下节点:
    /// </para>
    /// <para>
    /// add verb="GET,POST" path="ajaxhandler.ashx" type="Lvl.Web.AjaxHandler,Lvl"
    /// </para>
    /// </summary>
    public static class AjaxUtility
    {
        /// <summary>
        /// 客户端方法信息
        /// </summary>
        private class ClientCallbackMothod
        {
            /// <summary>
            ///  类型
            /// </summary>
            public string T { get; set; }
            /// <summary>
            /// 方法
            /// </summary>
            public string M { get; set; }

            /// <summary>
            /// 程序集
            /// </summary>
            public string A { get; set; }
        }

        private static Dictionary<string, string> AjaxTypeDict = new Dictionary<string, string>();

        private const string callbackscript = "/*Type,Args,Method[post,get],DataType(json|jsonp),Form,Callback,URL(JSONP),Callback(JSONP) */\nfunction CallServer(t,a,m,x,f,c,e,u,b){{if(x!='jsonp'){{if(f){{jQuery(f).ajaxSubmit({{url:'{0}?_dp='+Math.random(),type:m,dataType:x,headers:{{'ajaxtype':t}},data:{{ajaxtype:t,ajaxdata:jQuery.toJSON(a)}},success:c,error:(e !== undefined)?e:function(r,s,rc){{alert(r.responseText);}}}});}}else{{jQuery.ajax({{url:'{0}?_dp='+Math.random(),type:m,dataType:x,headers:{{'ajaxtype':t}},data:{{ajaxdata:jQuery.toJSON(a)}},success:c,error:(e !== undefined)?e:function(r,s,rc){{alert(r.responseText);}}}});}}}}else{{ jQuery.ajax({{url:u,type:m,dataType:x,jsonpCallback:b||'JsonpCallback',data:{{ajaxdata:jQuery.toJSON(a),ajaxtype:t}},success:c,error:(e !== undefined)?e:function(r,s,rc){{alert(r.responseText);}}}})}};}}\n";

        private static string ParseAjaxType(Type type, System.Web.UI.Page page)
        {
            if (!AjaxTypeDict.ContainsKey(type.FullName))
            {
                StringBuilder callbackmethod = new StringBuilder();
                foreach (MethodInfo mi in type.GetMethods())
                {
                    if (Reflect.HasAttribute<BaseAjaxMethodAttribute>(mi, true))
                    {
                        BaseAjaxMethodAttribute xattr = Reflect.GetAttribute<BaseAjaxMethodAttribute>(mi, true);
                        if (xattr is AjaxMethodAttribute)
                        {
                            AjaxMethodAttribute attr = xattr as AjaxMethodAttribute;
                            string callback = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(string.Format("{{\"T\":\"{0}\",\"A\":\"{1}\",\"M\":\"{2}\"}}", type.FullName, type.Assembly.GetName().Name, mi.Name)));
                            callbackmethod.AppendFormat(@"function {0}(", string.IsNullOrWhiteSpace(attr.ClientFunction) ? mi.Name : attr.ClientFunction);
                            ParameterInfo[] pis = mi.GetParameters();
                            string parms = pis.JoinToString(x => x.Name);
                            bool form = xattr is AjaxFormMethodAttribute;

                            callbackmethod.AppendFormat("{0}{1}{2}ajaxcbf,ajaxerr)", parms, parms.Length > 0 ? "," : "", form ? "form," : "");
                            callbackmethod.AppendFormat(@"{{CallServer('{0}',{1},'{2}','{3}',{4},ajaxcbf,ajaxerr,null);}}",
                                callback,
                                parms.Length > 0 ? "[" + parms + "]" : "null",
                                attr.RequestType,
                                attr.SerializeType.ToString().ToLower(),
                                form ? "form" : "null");
                            callbackmethod.AppendLine();
                        }
                        else
                        {
                            JsonpMethodAttribute attr = xattr as JsonpMethodAttribute;
                            string callback = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(string.Format("{{\"T\":\"{0}\",\"A\":\"{1}\",\"M\":\"{2}\"}}", type.FullName, type.Assembly.GetName().Name, mi.Name)));
                            callbackmethod.AppendFormat(@"function {0}(", string.IsNullOrWhiteSpace(attr.ClientFunction) ? mi.Name : attr.ClientFunction);
                            ParameterInfo[] pis = mi.GetParameters();
                            string parms = string.Empty;
                            foreach (ParameterInfo pi in pis)
                            {
                                if (parms.Length > 0)
                                {
                                    parms += ",";
                                }
                                parms += pi.Name;
                            }
                            callbackmethod.AppendFormat("{0}{1}", parms, parms.Length > 0 ? ",ajaxcbf,ajaxerr)" : "ajaxcbf,ajaxerr)");
                            callbackmethod.AppendFormat(@"{{CallServer('{0}',{1},'{2}','{3}',ajaxcbf,ajaxerr,'{4}',{5});}}", callback, parms.Length > 0 ? "[" + parms + "]" : "null", attr.RequestType, attr.SerializeType.ToString().ToLower(), attr.AjaxHandlerUrl, string.IsNullOrEmpty(attr.Callback) ? "null" : attr.Callback);
                            callbackmethod.AppendLine();
                        }
                    }
                }
                AjaxTypeDict.Add(type.FullName, callbackmethod.ToString());
            }
            return AjaxTypeDict[type.FullName];
        }

        /// <summary>
        /// 把AJAX类注册到页面中
        /// </summary>
        /// <param name="page">页面</param>
        /// <param name="lib">使用哪种框架</param>
        /// <param name="type">要注册的Ajax类</param>
        /// <param name="others">要注册的其它Ajax类</param>
        public static void RegisterAjaxType(System.Web.UI.Page page, Type type, params Type[] others)
        {
            if (page != null)
            {
                #region 回调方法
                if (!page.ClientScript.IsClientScriptBlockRegistered("AjaxApi"))
                {
                    //注册JQueryFrom和JSON解析器
                    page.ClientScript.RegisterClientScriptResource(typeof(PTA.Web.AjaxUtility), "PTA.Res.Web.JQueryForm.js");

                    //添加回调方法
                    StringBuilder ajaxutil = new StringBuilder().AppendFormatLine(callbackscript, page.ResolveUrl("~/AjaxHandler.ashx"));
                    page.ClientScript.RegisterClientScriptBlock(page.GetType(), "PTA.Ajax.AjaxApi", ajaxutil.ToString(), true);
                }
                #endregion
                string scriptName = "PTA.Ajax.AjaxApi." + type.FullName;
                if (!page.ClientScript.IsClientScriptBlockRegistered(scriptName))
                {
                    page.ClientScript.RegisterClientScriptBlock(page.GetType(), scriptName + type.FullName, ParseAjaxType(type, page), true);
                }
                foreach (var t in others)
                {
                    scriptName = "PTA.Ajax.AjaxApi." + t.FullName;
                    if (!page.ClientScript.IsClientScriptBlockRegistered(scriptName))
                    {
                        page.ClientScript.RegisterClientScriptBlock(page.GetType(), scriptName + t.FullName, ParseAjaxType(t, page), true);
                    }
                }
            }
        }

        /// <summary>
        /// 处理Ajax请求的Http上下文
        /// </summary>
        /// <param name="context">Http请求上下文</param>
        /// <returns>请求的处理结果</returns>
        public static void ProcessRequest(System.Web.HttpContext context)
        {
            context.Response.ContentEncoding = context.Request.ContentEncoding;
            var ajaxtype = context.Request.Headers["ajaxtype"] ?? context.Request["ajaxtype"];
            var ajaxdata = context.Request["ajaxdata"];
            object result = null, responsetext = string.Empty;
            if (!string.IsNullOrEmpty(ajaxtype) && !string.IsNullOrEmpty(ajaxdata))
            {
                ClientCallbackMothod ctm = Convert.FromJson<ClientCallbackMothod>(Encoding.UTF8.GetString(Convert.FromBase64String(ajaxtype)));
                object[] ajaxparms = Convert.FromJson<object[]>(ajaxdata);
                var callback = context.Request["callback"];

                MethodInfo ajaxMethod = Type.GetType(ctm.T + "," + ctm.A).GetMethod(ctm.M);
                AjaxMethodAttribute ajaxattr = Reflect.GetAttribute<AjaxMethodAttribute>(ajaxMethod, true);
                bool checkpass = ajaxattr.CheckAuth ? (Auth.AuthManage.Instance != null && Auth.AuthManage.Instance.CheckAuth(ajaxattr.AuthUrl, PTA.Web.WebContext.CurrentUser) && Auth.AuthManage.Instance.CheckAuth(ajaxattr.AuthPrvg, PTA.Web.WebContext.CurrentUser)) : true;
                if (checkpass)
                {
                    try
                    {
                        ParseParameter(ajaxMethod, ajaxparms);
                        object instance = ajaxMethod.IsStatic ? null : ajaxMethod.DeclaringType.Assembly.CreateInstance(ctm.T);
                        result = ajaxMethod.Invoke(instance, ajaxparms);
                    }
                    catch (Exception ex)
                    {
                        result = (new ServiceResult(false, ex.Message)).Add("ErrCode", 8888);
                    }
                }
                else
                {
                    result = (new ServiceResult(false, "操作错误，没有权限。")).Add("ErrCode", 9999);
                }
                if (string.IsNullOrEmpty(callback))
                {
                    var attr = ajaxattr as AjaxMethodAttribute;
                    if (!string.IsNullOrWhiteSpace(attr.ContentType))
                    {
                        context.Response.ContentType = attr.ContentType.Trim();
                    }
                    switch (attr.SerializeType)
                    {
                        case SerializeType.XML:
                            responsetext = Convert.ToXml(result);
                            break;
                        case SerializeType.JSON:
                            responsetext = Newtonsoft.Json.JsonConvert.SerializeObject(result, dtc);
                            break;
                        default:
                            responsetext = result is string ? (string)result : result.ToString();
                            break;
                    }
                }
                else
                {
                    responsetext = string.Format("{0}({1})", callback, Convert.ToJson(result));
                }
            }
            else
            {
                context.Response.StatusCode = 400;
                responsetext = (new ServiceResult(false, "没有找到要调用的方法")).Add("ErrCode", 8888).ToJson();
            }
            context.Response.Write(responsetext);
            context.ApplicationInstance.CompleteRequest();
        }

        /// <summary>
        /// 把Object参数转换为方法需要的类型
        /// </summary>
        /// <param name="mi">方法</param>
        /// <param name="ps">参数列表</param>
        private static void ParseParameter(MethodInfo mi, object[] ps)
        {
            ParameterInfo[] pis = mi.GetParameters();
            if (pis.Length > 0 && pis.Length == ps.Length && ps != null)
            {
                for (int i = 0; i < pis.Length; i++)
                {
                    if (ps[i] == null)
                    {
                        continue;
                    }
                    try
                    {
                        ps[i] = Convert.ChangeType(ps[i], pis[i].ParameterType);
                    }
                    catch
                    {
                        try
                        {
                            ps[i] = Convert.FromJson(ps[i].ToString(), pis[i].ParameterType);
                        }
                        catch (Exception ex)
                        {
                            if (Reflect.HasAttribute<ParameterParseAttribute>(pis[i]))
                            {
                                throw new Exception(Reflect.GetAttribute<ParameterParseAttribute>(pis[i]).ErrorText);
                            }
                            throw ex;
                        }
                    }
                }
            }
        }

        private static Newtonsoft.Json.Converters.DateTimeConverter dtc = new Newtonsoft.Json.Converters.DateTimeConverter();
    }

    public class ServiceResult : Dictionary<string, object>
    {
        #region 私有数据
        public ServiceResult()
        {
            this.Add("Success", true);
            this.Add("Message", string.Empty);
        }

        public ServiceResult(bool success, string message)
        {
            this.Add("Success", success);
            this.Add("Message", message);
        }
        #endregion

        #region 添加一个键值
        /// <summary>
        /// 添加一个键值
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public new ServiceResult Add(string key, object value)
        {
            if (base.ContainsKey(key))
            {
                base[key] = value;
            }
            else
            {
                base.Add(key, value);
            }
            return this;
        }
        #endregion

        #region 是否成功
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success
        {
            get { return (bool)this["Success"]; }
            set { this["Success"] = value; }
        }
        #endregion

        #region 消息
        /// <summary>
        /// 消息
        /// </summary>
        public string Message
        {
            get
            {
                return (string)this["Message"];
            }
            set
            {
                this["Message"] = value;
            }
        }
        #endregion

    }

    public class ServiceResult<T> : ServiceResult
    {
        #region 构造函数
        public ServiceResult()
        {
            this["Data"] = default(T);
        }

        public ServiceResult(T data)
        {
            this["Data"] = data;
        }
        public ServiceResult(bool success, string message, T data)
            : base(success, message)
        {
            this["Data"] = data;
        }
        #endregion

        #region 返回数据
        /// <summary>
        /// 返回数据
        /// </summary>
        public T Data
        {
            get
            {
                return (T)this["Data"];
            }
            set
            {
                this["Data"] = value;
            }
        }
        #endregion
    }
}
